//Copyright 2011 New York City 3D Community

//This file is part of New York City 3D.

//New York City 3D is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//New York City 3D is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with New York City 3D.  If not, see <http://www.gnu.org/licenses/>.

package nyc3d.vehicle;

import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.system.AppSettings;
import nyc3d.NYC3D;
//import nyc3d.ai.*;

/**
 *
 * A driveable "FancyCar" object.
 *
 */

public abstract class Vehicle implements ActionListener {

    private VehicleControl player;
    private float wheelRadius;
    private float steeringValue=0;
    private float accelerationValue=0;
    private Vector3f defaultLocation, defaultRotation;
    private Node carNode;
    //Driver driver;

 /**
 * Creates a driveable "FancyCar" object at default location (0, 0, 0).
 * The url argument must specify an absolute
  * @param settings
  * @param inputManager
  * @param viewPort
  * @param assetManager
  * @param pSpace
  * @param rootNode
  */
    public Vehicle(String name, String model, Vector3f location, Vector3f rotation, AppSettings settings, InputManager inputManager, ViewPort viewPort, AssetManager assetManager, Node parentNode, PhysicsSpace pSpace){

        defaultLocation = location.clone();
        defaultRotation = rotation.clone();

        if (settings.getRenderer().startsWith("LWJGL")){
            BasicShadowRenderer bsr = new BasicShadowRenderer(assetManager, 512);
            bsr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
            viewPort.addProcessor(bsr);
        }

        setupKeys(inputManager);
        buildPlayer(name, model, assetManager, parentNode, pSpace);

    }

    private void setupKeys(InputManager inputManager) {
        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addListener(this,"Lefts");
        inputManager.addListener(this,"Rights");
        inputManager.addListener(this,"Ups");
        inputManager.addListener(this,"Downs");
        inputManager.addListener(this,"Space");
        inputManager.addListener(this,"Reset");
    }

    private Geometry findGeom(Spatial spatial, String name){
        if (spatial instanceof Node){
            Node node = (Node) spatial;
            for (int i = 0; i < node.getQuantity(); i++){
                Spatial child = node.getChild(i);
                Geometry result = findGeom(child, name);
                if (result != null)
                    return result;
            }
        }else if (spatial instanceof Geometry){
            if (spatial.getName().startsWith(name))
                return (Geometry) spatial;
        }
        return null;
    }

    private void buildPlayer(String name, String model, AssetManager assetManager, Node parentNode, PhysicsSpace pSpace) {
        float stiffness = 120.0f;//200=f1 car
        float compValue = 0.2f; //(lower than damp!)
        float dampValue = 0.3f;
        final float mass = 400;

        //Load model and get chassis Geometry
        carNode = (Node)assetManager.loadModel("Models/" + model + "/Car.j3o");
        carNode.setName(name);
        carNode.setShadowMode(ShadowMode.Cast);
        Geometry chasis = findGeom(carNode, "Car");
        chasis.setName(name);
        BoundingBox box = (BoundingBox) chasis.getModelBound();

        //Create a hull collision shape for the chassis
        CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);

        //Create a vehicle control
        player = new VehicleControl(carHull, mass);

        //Setting default values for wheels
        player.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
        player.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
        player.setSuspensionStiffness(stiffness);
        player.setMaxSuspensionForce(10000);

        //Create four wheels and add them at their locations
        //note that our fancy car actually goes backwards..
        Vector3f wheelDirection = new Vector3f(0, -1, 0);
        Vector3f wheelAxle = new Vector3f(-1, 0, 0);

        Geometry wheel_fr = findGeom(carNode, "WheelFrontRight");
        wheel_fr.center();
        box = (BoundingBox) wheel_fr.getModelBound();
        wheelRadius = box.getYExtent();
        float back_wheel_h = (wheelRadius * 1.7f) - 1f;
        float front_wheel_h = (wheelRadius * 1.9f) - 1f;
        player.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);

        Geometry wheel_fl = findGeom(carNode, "WheelFrontLeft");
        wheel_fl.center();
        box = (BoundingBox) wheel_fl.getModelBound();
        player.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);

        Geometry wheel_br = findGeom(carNode, "WheelBackRight");
        wheel_br.center();
        box = (BoundingBox) wheel_br.getModelBound();
        player.addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);

        Geometry wheel_bl = findGeom(carNode, "WheelBackLeft");
        wheel_bl.center();
        box = (BoundingBox) wheel_bl.getModelBound();
        player.addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);

        player.getWheel(2).setFrictionSlip(4);
        player.getWheel(3).setFrictionSlip(4);
        parentNode.attachChild(carNode);
        pSpace.add(player);
        player.setPhysicsLocation(defaultLocation);
        carNode.setLocalTranslation(defaultLocation);

        Matrix3f rot = new Matrix3f();
        rot.fromAngleAxis(defaultRotation.x, Vector3f.UNIT_X);
        rot.fromAngleAxis(defaultRotation.y, Vector3f.UNIT_Y);
        rot.fromAngleAxis(defaultRotation.z, Vector3f.UNIT_Z);
        carNode.rotate(defaultRotation.x, defaultRotation.y, defaultRotation.z);
        player.setPhysicsRotation(rot);
        carNode.addControl(player);
    }

    public void onAction(String binding, boolean value, float tpf) {
        if (!NYC3D.menuActive && NYC3D.status.equals(NYC3D.Status.CAR) && NYC3D.currentCar.equals(this) ){
            if (binding.equals("Lefts")) {
            if(value)
                steeringValue+=.5f;
            else
                steeringValue+=-.5f;
            player.steer(steeringValue);
        } else if (binding.equals("Rights")) {
            if(value)
                steeringValue+=-.5f;
            else
                steeringValue+=.5f;
            player.steer(steeringValue);
        }
        //note that our fancy car actually goes backwards..
        else if (binding.equals("Ups")) {
            if(value)
                accelerationValue-=800;
            else
                accelerationValue+=800;
            player.accelerate(accelerationValue);
        } else if (binding.equals("Downs")) {
            if(value)
                player.brake(40f);
            else
                player.brake(0f);
        } else if (binding.equals("Reset")) {
            if (value) {
                System.out.println("Reset");
                player.setPhysicsLocation(defaultLocation);
                carNode.setLocalTranslation(defaultLocation);

                Matrix3f rot = new Matrix3f();
                rot.fromAngleAxis(defaultRotation.x, Vector3f.UNIT_X);
                rot.fromAngleAxis(defaultRotation.y, Vector3f.UNIT_Y);
                rot.fromAngleAxis(defaultRotation.z, Vector3f.UNIT_Z);
                carNode.rotate(defaultRotation.x, defaultRotation.y, defaultRotation.z);
                player.setPhysicsRotation(rot);
                
                player.setLinearVelocity(Vector3f.ZERO);
                player.setAngularVelocity(Vector3f.ZERO);
                player.resetSuspension();
            } else {
            }
        }
        }
    }

    public VehicleControl getPlayer() {
        return player;
    }

    public void setPlayer(VehicleControl player) {
        this.player = player;
    }

    public float getAccelerationValue() {
        return accelerationValue;
    }

    public void setAccelerationValue(float accelerationValue) {
        this.accelerationValue = accelerationValue;
    }

    public float getSteeringValue() {
        return steeringValue;
    }

    public void setSteeringValue(float steeringValue) {
        this.steeringValue = steeringValue;
    }

    public Node getCarNode() {
        return carNode;
    }

    public void setCarNode(Node carNode) {
        this.carNode = carNode;
    }
}